package controller

import (
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"path"
	"strconv"
	"strings"
	"time"

	"chrent.com/ServiceManage/boot/ds"
	"chrent.com/ServiceManage/boot/setting"
	"chrent.com/ServiceManage/models"
	"chrent.com/ServiceManage/pm2"
	"chrent.com/ServiceManage/utils"
	"github.com/creack/pty"
	"github.com/gen2brain/go-unarr"
	"github.com/gin-gonic/gin"
	"github.com/gin-gonic/gin/binding"
	"github.com/golang-module/dongle"
	pm2app "github.com/ntt360/pmon2/app"
	"github.com/ntt360/pmon2/app/model"
	"github.com/ntt360/pmon2/app/svc/process"
)

func List(ctx *gin.Context) {
	name := ctx.Query("name")
	var all []model.Process
	err := ds.Db().Where("name like ? or process_file like ?", "%"+name+"%", "%"+name+"%").Find(&all).Error
	if err != nil {
		ctx.JSON(500, err.Error())
		return
	}
	for i, _ := range all {
		m := &all[i]
		if m.Status == "running" {
			isRunning := utils.IsRunning(m.Pid, m.Name)
			if !isRunning {
				m.Status = model.StatusFailed
				ds.Db().Save(m)
			}
			continue
		}
	}
	ctx.JSON(200, RtnData("成功", all))
}
func Add(ctx *gin.Context) {
	pm2app.Instance(utils.GetPm2Conf())
	task := models.Task{}
	err := ctx.BindJSON(&task)
	if err != nil {
		ctx.JSON(500, err.Error())
		return
	}

	if task.Id > 0 {
		ds.Db().Omit("log_dir").Save(&task)
		m := model.Process{}
		err = ds.Db().First(&m, task.Id).Error
		if err != nil {
			ctx.JSON(500, err.Error())
			return
		}
		m.Log = task.Log
		m.Args = task.Args
		m.AutoRestart = !task.NoAutoRestart
		m.Username = task.User
		ds.Db().Save(&m)
		ctx.JSON(200, RtnData("成功", m))
	} else {
		task.Name, err = pm2.GetExecFile(task.Name)
		if err != nil {
			ctx.JSON(500, err.Error())
			return
		}
		m, exist := pm2.ProcessExist(task.Name)
		execfile := task.Name
		var rel []string
		if exist {
			rel, err = pm2.Restart(m, task.ExecFlags.Json())
			if err == nil {
				task.Id, err = strconv.Atoi(rel[0])
				ds.Db().Save(&task)
			}
		} else {
			taskPaths := strings.Split(task.Name, string(os.PathSeparator))
			task.ExecFlags.Name = taskPaths[len(taskPaths)-1]
			rel, err = pm2.LoadFirst(execfile, task.ExecFlags.Json())
			if err == nil {
				task.Id, err = strconv.Atoi(rel[0])
				ds.Db().Create(&task)
				m1 := model.Process{}
				ds.Db().First(&m1, task.Id)
				m = &m1
			}
		}
		if err != nil {
			ctx.JSON(500, err.Error())
			return
		}
		ctx.JSON(200, RtnData("成功", m))
	}

}
func Edit(ctx *gin.Context) {
	id := ctx.Param("id")
	rtn := models.Task1{}
	ds.Db().Raw("select p.*,t.upload_dir,t.beforce,t.after,t.ext_to_dir,t.sys_ctr,t.sys_build,t.desc "+
		"from task as t "+
		"inner join process p on t.id=p.id where p.id=?", id).Scan(&rtn)
	ctx.JSON(200, RtnData("成功", rtn))
}
func Start(ctx *gin.Context) {
	id := ctx.Param("id")
	var m model.Process
	if err := ds.Db().First(&m, "id = ?", id).Error; err != nil {
		ctx.JSON(500, err.Error())
		return
	}
	// checkout process state
	if process.IsRunning(m.Pid) {
		if m.Status != model.StatusRunning {
			m.Status = model.StatusRunning
			ds.Db().Save(&m)
		}
		ctx.JSON(200, RtnData("任务已在运行中"))
		return
	}
	rel, err := process.TryStart(m, "")
	if err != nil {
		ctx.JSON(500, err.Error())
		return
	}
	if len(rel) < 4 {
		ctx.JSON(500, fmt.Sprintf("启动异常,TryStart返还异常!,rel:%v", rel))
		return
	}
	time.Sleep(1 * time.Second)
	m.Pid, _ = strconv.Atoi(rel[2])
	m.Status = rel[3]
	if !process.IsRunning(m.Pid) {
		ctx.JSON(500, "启动异常,请检查日志")
		return
	}

	ctx.JSON(200, RtnData("成功", m))
}
func Restart(ctx *gin.Context) {
	id := ctx.Param("id")
	m, err := pm2.ForceRestart(id)
	if err != nil {
		ctx.JSON(500, err.Error())
		return
	}
	ctx.JSON(200, RtnData("成功", m))
}
func Stop(ctx *gin.Context) {
	id := ctx.Param("id")
	f := ctx.DefaultQuery("force", "true")
	p, err := pm2.Stop(id, f == "true")
	if err != nil {
		ctx.JSON(500, err.Error())
		return
	}
	ctx.JSON(200, RtnData("成功", p))
}
func Del(ctx *gin.Context) {
	id := ctx.Param("id")
	var m model.Process
	err := ds.Db().First(&m, "id = ?", id).Error
	if err != nil {
		ctx.JSON(500, err.Error())
		return
	}

	if m.Status == model.StatusRunning {
		ctx.JSON(500, "必需先停止任务")
		return
	}
	ds.Db().Delete(&models.Task{Ext: models.Ext{Id: int(m.ID)}})
	ds.Db().Delete(&m)
	_ = os.Remove(m.Log)
	ctx.JSON(200, RtnData("成功"))
}
func Update(ctx *gin.Context) {
	task1 := models.Task{}
	err := ctx.ShouldBindWith(&task1, binding.JSON)
	if err != nil {
		ctx.JSON(500, err.Error())
		return
	}
	err = ds.Db().Table("task").Where("id=?", task1.Id).Updates(map[string]any{
		"upload_dir": task1.UploadDir,
		"beforce":    task1.Beforce,
		"after":      task1.After,
	}).Error
	if err != nil {
		ctx.JSON(500, err.Error())
		return
	}
	ctx.JSON(200, RtnData("成功"))
}
func RtnData(message string, data ...any) gin.H {
	h := gin.H{
		"code":    200,
		"result":  nil,
		"message": message,
	}
	if len(data) > 0 {
		h["result"] = data[0]
	}
	return h
}
func UserInfo(ctx *gin.Context) {
	user, ok := ctx.Get(models.UserKey)
	if !ok {
		ctx.JSON(500, "未登录或登录超时")
		return
	}
	u, _ := user.(*models.User)
	ds.Db().Raw("select id,name,email from user where id=?", u.Id).Scan(u)
	ctx.JSON(200, RtnData("成功", u))
	return
}
func UserChange(ctx *gin.Context) {
	user, ok := ctx.Get(models.UserKey)
	if !ok {
		ctx.JSON(500, "未登录或登录超时")
		return
	}
	u, _ := user.(*models.User)
	req := map[string]any{}
	err := ctx.ShouldBindWith(&req, binding.JSON)
	if err != nil {
		ctx.JSON(500, err.Error())
		return
	}
	err = ds.Db().Raw("select * from user where id=?", u.Id).Scan(u).Error
	if err != nil {
		ctx.JSON(500, err.Error())
		return
	}
	oldpass := req["oldpass"].(string)
	newpass := req["newpass"].(string)
	newpass_confirm := req["newpass_confirm"].(string)
	if newpass != newpass_confirm {
		ctx.JSON(500, "二次输入的密码不一至！请检查")
		return
	}
	if !dongle.Verify.FromRawString(u.Password, oldpass).ByBcrypt().ToBool() {
		ctx.JSON(500, "原密码错误")
		return
	}
	u.Password = dongle.Sign.FromString(newpass).ByBcrypt().ToRawString()
	u.Email = req["email"].(string)
	err = ds.Db().Save(u).Error
	if err != nil {
		ctx.JSON(500, err.Error())
		return
	}
	ctx.JSON(200, RtnData("成功"))
}
func ReloadStatus(ctx *gin.Context) {
	pm2.ReloadStatus()
	ctx.JSON(200, RtnData("成功"))
}
func Upgrade(ctx *gin.Context) {
	task1 := models.Task1{}
	err := ctx.ShouldBind(&task1)
	if err != nil {
		ctx.JSON(500, err.Error())
		return
	}
	doUpgrade(&task1, ctx)
	return
}
func UpgradeFromCurl(ctx *gin.Context) {
	email := ctx.PostForm("email")
	password := ctx.PostForm("password")
	user := models.User{}
	if err := ds.Db().Where("email=?", email).First(&user).Error; err != nil {
		ctx.JSON(500, "用户不存在")
		return
	}
	if !dongle.Verify.FromRawString(user.Password, password).ByBcrypt().ToBool() {
		ctx.JSON(500, "密码错误")
		return
	}

	processId := ctx.PostForm("id")
	if processId == "" {
		ctx.JSON(500, "未取得id")
		return
	}
	task1 := models.Task1{}

	if err := ds.Db().Raw("select p.*,t.upload_dir,t.beforce,t.after,t.ext_to_dir,t.sys_ctr,sys_build "+
		"from task as t "+
		"inner join process p on t.id=p.id where p.id=?", processId).Scan(&task1).Error; err != nil {
		ctx.JSON(500, err.Error())
		return
	}
	doUpgrade(&task1, ctx)
	return
}

func doUpgrade(task1 *models.Task1, ctx *gin.Context) {
	message := "成功"
	defer func() {
		if p := recover(); p != nil {
			ctx.JSON(500, fmt.Sprintf("%s", p))
		}
	}()

	// Create a shell command
	cmd := "/bin/sh"
	// Start a shell with pty
	shell, err := pty.StartWithSize(exec.Command(cmd), &pty.Winsize{
		Rows: 30,
		Cols: 120,
		X:    640,
		Y:    480,
	})
	if err != nil {
		ctx.JSON(500, err.Error())
		return
	}
	defer shell.Close()

	// Set initial environment
	fmt.Fprintf(shell, "export HOME=%s\n", setting.Conf.HomePath)

	// Upload upgrade package
	file, err := ctx.FormFile("file")
	if err != nil {
		ctx.JSON(500, err.Error())
		return
	}
	fileName := file.Filename
	if _, err = os.Stat(task1.UploadDir); os.IsNotExist(err) {
		err = os.MkdirAll(task1.UploadDir, os.ModePerm)
		if err != nil {
			ctx.JSON(500, err.Error())
			return
		}
	}
	cpFileName := path.Join(task1.UploadDir, fileName)
	err = ctx.SaveUploadedFile(file, cpFileName)
	if err != nil {
		ctx.JSON(500, err.Error())
		return
	}

	// Execute pre-extraction commands
	if task1.Beforce != "" {
		bcs := strings.Split(task1.Beforce, "\n")
		for _, cmd := range bcs {
			if strings.TrimSpace(cmd) != "" {
				fmt.Fprintf(shell, "%s\n", cmd)
			}
		}
	}

	// System extraction
	if task1.SysCtr {
		a, err := unarr.NewArchive(cpFileName)
		if err != nil {
			ctx.JSON(500, err.Error())
			return
		}
		_, err = a.Extract(task1.ExtToDir)
		if err != nil {
			ctx.JSON(500, err.Error())
			return
		}
	}

	// System build
	if task1.SysBuild {
		tmp := strings.Split(task1.ProcessFile, string(os.PathSeparator))
		outname := tmp[len(tmp)-1]
		cpCmd := fmt.Sprintf("%s build -buildvcs=false -o %s", setting.Conf.Gobin, outname)
		fmt.Fprintf(shell, "cd %s\n", task1.ExtToDir)
		fmt.Fprintf(shell, "%s\n", cpCmd)
	}

	// Execute post-extraction commands
	if task1.After != "" {
		bcs := strings.Split(task1.After, "\n")
		for _, cmd := range bcs {
			if strings.TrimSpace(cmd) != "" {
				fmt.Fprintf(shell, "%s\n", cmd)
			}
		}
	}

	// Exit the shell
	fmt.Fprintf(shell, "exit\n")

	// Read shell output
	var befRtn []string
	buf := make([]byte, 1024)
	for {
		n, err := shell.Read(buf)
		if err != nil {
			if err == io.EOF {
				break
			}
			// Handle other errors
			break
		}
		if n > 0 {
			befRtn = append(befRtn, string(buf[:n]))
		}
	}

	// Restart service
	if task1.AutoRestart {
		idstr := strconv.Itoa(int(task1.Id))
		m, err := pm2.ForceRestart(idstr)
		if err != nil {
			message = fmt.Sprintf("上传编译成功,但重启错误:%s", err.Error())
		} else {
			task1.Pid = m.Pid
		}
	}

	// Write log to file
	lf := fmt.Sprintf("log_%d_%s.log", task1.Id, time.Now().Format("20060102150405"))
	logFile := path.Join(utils.GetRootDir(), "logs", lf)
	f, err := os.OpenFile(logFile, os.O_CREATE|os.O_WRONLY, 0644)
	if err != nil {
		ctx.JSON(500, err.Error())
		return
	}
	defer f.Close()

	// Write task1 to log
	task1Json, err := json.Marshal(task1)
	if err != nil {
		ctx.JSON(500, err.Error())
		return
	}
	f.WriteString(string(task1Json) + "\n")

	// Write shell output to log
	for _, str := range befRtn {
		f.WriteString(fmt.Sprintf("%s\n", str))
	}

	// Extract run log filename
	tmp := strings.Split(task1.Log, string(os.PathSeparator))
	runlog := ""
	if len(tmp) > 0 {
		runlog = tmp[len(tmp)-1]
		runlog = strings.ReplaceAll(runlog, ".log", "")
	}

	ctx.JSON(200, RtnData(message, gin.H{
		"update_log": fmt.Sprintf("%s/#/showlog/%s", setting.Conf.Domain, strings.ReplaceAll(lf, ".log", "")),
		"run_log":    fmt.Sprintf("%s/#/showlog/%s", setting.Conf.Domain, runlog),
	}))
}
func Log(ctx *gin.Context) {
	id := ctx.Param("id")
	if id == "" {
		ctx.JSON(500, "未取得id")
		return
	}
	//读取logs下名为id的文件
	logFile := path.Join(utils.GetRootDir(), "logs", id+".log")
	if _, err := os.Stat(logFile); os.IsNotExist(err) {
		ctx.JSON(500, "日志文件不存在")
		return
	}
	//读取文件内容
	content, err := ioutil.ReadFile(logFile)
	if err != nil {
		ctx.JSON(500, err.Error())
		return
	}
	ctx.JSON(200, RtnData("成功", gin.H{
		"content": string(content),
	}))
	return
}
